pacenotes_installer\engine\execute/
uninstall_package.rs1pub fn uninstall_package(
3 run: &mut super::run::RunContext,
4 key: &str,
5 outcome: &mut super::Outcome,
6) -> Result<(), super::ExecuteError> {
7 let Some(entry) = run.ledger.get(key).cloned() else {
8 return Ok(());
9 };
10 let identical =
13 |file: &crate::ledger::LedgerFile| file.prior == crate::ledger::Prior::Identical;
14 let installed = entry.state == crate::ledger::EntryState::Installed;
15 let payload = if !installed && entry.files.values().any(identical) {
16 std::fs::read(run.cache.payload_path(&entry.sha256)).ok()
17 } else {
18 None
19 };
20 let archive = payload
21 .as_deref()
22 .and_then(|payload| crate::zip::Archive::open(payload).ok());
23 for file in entry.files.values() {
24 if let Some(reason) = put_back(run, key, &entry, file, archive.as_ref())? {
25 run.watcher
26 .log(&format!("{} {reason}; left alone", file.path));
27 outcome.left_alone.push(file.path.clone());
28 }
29 }
30 prune_empty_dirs(run.game_dir, &entry);
31 run.ledger
32 .remove(key)
33 .map_err(super::ExecuteError::ledger)?;
34 outcome.uninstalled.push(key.to_string());
35 run.watcher.log(&format!("removed {key}"));
36 Ok(())
37}
38
39fn put_back(
42 run: &mut super::run::RunContext,
43 key: &str,
44 entry: &crate::ledger::LedgerEntry,
45 file: &crate::ledger::LedgerFile,
46 payload: Option<&crate::zip::Archive>,
47) -> Result<Option<String>, super::ExecuteError> {
48 let target = crate::relative_path::join(run.game_dir, &file.path);
49 if !target.exists() {
50 return Ok(None);
51 }
52 let others: Vec<&str> = run
53 .ledger
54 .owners_of(&file.path)
55 .into_iter()
56 .filter(|owner| *owner != key)
57 .collect();
58 if !others.is_empty() {
59 let line = format!(
61 "{} is still used by {}; left in place",
62 file.path,
63 others.join(", ")
64 );
65 run.watcher.log(&line);
66 return Ok(None);
67 }
68 if entry.state == crate::ledger::EntryState::Installed {
70 match crate::sha256::hex_digest_of_file(&target) {
71 Ok(found) if found == file.sha256 => {}
72 Ok(_) => return Ok(Some("has changed since it was installed".into())),
73 Err(e) => return Ok(Some(format!("could not be read ({e})"))),
74 }
75 }
76
77 match &file.prior {
78 crate::ledger::Prior::Absent => super::game_files::remove_file(&target)?,
79 crate::ledger::Prior::Backup { point } => {
80 let copy = super::restore_point::saved_copy(run.game_dir, point, &file.path);
81 if !copy.is_file() {
82 return Ok(Some(
83 "cannot be put back (its saved copy is missing)".into(),
84 ));
85 }
86 super::game_files::copy_file(©, &target)?;
87 }
88 crate::ledger::Prior::Identical if entry.state == crate::ledger::EntryState::Installed => {}
91 crate::ledger::Prior::Identical => {
92 let original = payload.and_then(|archive| archive.read(&file.path).ok());
93 let Some(bytes) = original else {
94 return Ok(Some(
95 "was already on this computer before it was installed, and the package \
96 it came from is no longer cached"
97 .into(),
98 ));
99 };
100 super::game_files::write_atomic(&target, &bytes)?;
101 }
102 }
103 Ok(None)
104}
105
106fn prune_empty_dirs(game_dir: &std::path::Path, entry: &crate::ledger::LedgerEntry) {
109 let mut dirs: Vec<std::path::PathBuf> = entry
110 .files
111 .values()
112 .filter_map(|file| {
113 crate::relative_path::join(game_dir, &file.path)
114 .parent()
115 .map(std::path::Path::to_path_buf)
116 })
117 .collect();
118 dirs.sort();
119 dirs.dedup();
120 dirs.sort_by_key(|dir| std::cmp::Reverse(dir.components().count()));
121 for dir in dirs {
122 let mut at = dir.as_path();
123 while at.starts_with(game_dir) && at != game_dir {
124 if std::fs::remove_dir(at).is_err() {
126 break;
127 }
128 match at.parent() {
129 Some(parent) => at = parent,
130 None => break,
131 }
132 }
133 }
134}